package chap_06;

import chap_04.Dish;
import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 归约和汇总
 */
public class Run_01 {
    List<Dish> menu = Arrays.asList(
            new Dish("pork", false, 800, Dish.Type.MEAT),
            new Dish("beef", false, 700, Dish.Type.MEAT),
            new Dish("chicken", false, 400, Dish.Type.MEAT),
            new Dish("french fries", true, 530, Dish.Type.OTHER),
            new Dish("rice", true, 350, Dish.Type.OTHER),
            new Dish("season fruit", true, 120, Dish.Type.OTHER),
            new Dish("pizza", true, 550, Dish.Type.OTHER),
            new Dish("prawns", false, 300, Dish.Type.FISH),
            new Dish("salmon", false, 450, Dish.Type.FISH));

    /**
     * 利用counting工厂方法返回的收集器，数一数菜单里有多少种菜
     */
    @Test
    public void demo_01() {
        // method1
        //long howManyDishes = menu.stream().count();
        long howManyDishes = menu.stream().collect(Collectors.counting());
        System.out.println(howManyDishes);

        // method2
        System.out.println(menu.stream()
                .count());

        // method3
        menu.stream()
                .map(d -> 1)
                .reduce(Integer::sum)
                .ifPresent(System.out::println);
    }

    /**
     * 查找流中的最大值和最小值
     * 你可以使用两个收集器，Collectors.maxBy和Collectors.minBy，来计算流中的最大或最小值。
     * 这两个收集器接收一个Comparator参数来比较流中的元素。
     */
    @Test
    public void demo_02() {
        Optional<Dish> collect = menu.stream()
                .collect(Collectors.maxBy(Comparator.comparing(Dish::getCalories)));
        collect.ifPresent(System.out::println);
    }

    /**
     * 汇总
     * Collectors类专门为汇总提供了一个工厂方法：Collectors.summingInt。它可接受一
     * 个把对象映射为求和所需int的函数，并返回一个收集器
     */
    @Test
    public void demo_03() {
        IntSummaryStatistics collect = menu.stream()
                .collect(Collectors.summarizingInt(Dish::getCalories));
        System.out.println(collect.getCount());
        System.out.println(collect.getMax());
        System.out.println(collect.getAverage());
        System.out.println(collect);
    }

    /**
     * 连接字符串
     */
    @Test
    public void demo_04() {
        String collect = menu.stream()
                .map(Dish::getName).collect(Collectors.joining(","));
        System.out.println(collect);
    }

    /**
     * 广义的归约汇总
     * 第一个参数是归约操作的起始值，也是流中没有元素时的返回值，所以很显然对于数值和而言0是一个合适的值。
     * 第二个参数将菜肴转换成一个表示其所含热量的int。
     * 第三个参数是一个BinaryOperator，将两个项目累积成一个同类型的值。这里它就是对两个int求和。
     */
    @Test
    public void demo_05() {
        Integer collect = menu.stream()
                .collect(Collectors.reducing(0, Dish::getCalories, (i, j) -> i + j));
        System.out.println(collect);

        Optional<Dish> optional = menu.stream()
                .collect(Collectors.reducing((Dish d1, Dish d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
        optional.ifPresent(System.out::println);
    }

    /**
     * Stream接口的collect和reduce方法有何不同
     */
    @Test
    public void demo_06() {
        Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
        List<Integer> numbers = stream.reduce(
                new ArrayList<>(),
                (List<Integer> l, Integer e) -> {
                    l.add(e);
                    System.out.println("l:" + l + "; e:" + e);
                    return l;
                },
                (List<Integer> l1, List<Integer> l2) -> {
                    l1.addAll(l2);
                    System.out.println("l1:" + l1 + ";l2:" + l2);
                    return l1;
                }
        );

        System.out.println(numbers);
        /*这个解决方案有两个问题：一个语义问题和一个实际问题。语义问题在于，reduce方法
        旨在把两个值结合起来生成一个新值，它是一个不可变的归约。与此相反，collect方法的设
        计就是要改变容器，从而累积要输出的结果。这意味着，上面的代码片段是在滥用reduce方
        法，因为它在原地改变了作为累加器的List。*/
    }
}
